<?php
namespace Core;

final class MCache {
	/**
	 * 无异常。
	 * @var int
	 */
	const ERROR_NONE = 0;
	/**
	 * 远程连接初始化失败。
	 * @var int
	 */
	const ERROR_INIT_FAIL = 1;
	/**
	 * 远程连接丢失。
	 * @var int
	 */
	const ERROR_CONN_LOSE = 2;
	/**
	 * 拒绝操作。
	 * @var int
	 */
	const ERROR_REJECT = 4;
	/**
	 * 未知异常。
	 * @var int
	 */
	const ERROR_UNKNOWN = 8;
	
	/**
	 * APC 驱动。
	 * @var string
	 */
	const DRIVER_APC = 'APC';
	/**
	 * Redis 驱动。
	 * @var string
	 */
	const DRIVER_REDIS = 'Redis';
	
	/**
	 * MCache 句柄列表。
	 * @var array
	 */
	private static $handle = [];
	/**
	 * 如果安装了 igbinary 扩展，则采用 igbinary 对非标量数据进行序列化。
	 * @var boolean
	 */
	private static $igbinary = NULL;

	/**
	 * 获得 MCache 操作句柄（对象）。
	 *
	 * @param string $mcsn
	 * @return MCache\Redis
	 */
	public static function getInstance($mcsn) {
		if (isset(self::$handle[$mcsn])) {
			return self::$handle[$mcsn];
		}
		
		if (!isset(HHO::$config['mcsn'][$mcsn])) {
			trigger_error("MCache '" . $mcsn . "' undefined", E_USER_WARNING);
			return NULL;
		}
		
		switch (strtoupper(HHO::$config['mcsn'][$mcsn]['driver'])) {
			case 'APC' :
				self::$handle[$mcsn] = new MCache\APC(HHO::$config['mcsn'][$mcsn]);
				break;
			case 'REDIS' :
				self::$handle[$mcsn] = new MCache\Redis($mcsn);
				break;
			default :
				trigger_error("MCache does not support '" . HHO::$config['mcsn'][$mcsn]['driver'] . "' drive", E_USER_ERROR);
				return NULL;
		}
		
		return self::$handle[$mcsn];
	}

	/**
	 * 对非标量数据进行序列化。
	 * - 提示：phpredis 的 Redis::OPT_SERIALIZER 设置无法仅针对非标量数据进行序列化，这样将导致 Redis::incr() 和 Redis::decr() 等操作无法进行。
	 *
	 * @param mixed $value
	 * @return string
	 */
	public static function serialize($value) {
		if (is_array($value) || is_object($value)) {
			is_null(self::$igbinary) && self::$igbinary = function_exists('igbinary_serialize');
			
			return self::$igbinary ? igbinary_serialize($value) : (chr(0) . chr(0) . chr(0) . chr(0) . serialize($value));
		}
		
		return $value;
	}

	/**
	 * 对非标量数据进行反序列化。
	 *
	 * @param string $value
	 * @return mixed
	 */
	public static function unserialize($value) {
		if (is_string($value)) {
			if (ord($value[0]) === 0 && ord($value[1]) === 0 && ord($value[2]) === 0) {
				is_null(self::$igbinary) && self::$igbinary = function_exists('igbinary_serialize');
				
				/**
				 * 分析 IGBINARY 源码包的若干备忘：
				 *
				 * - 版本：1.1.2-dev
				 * - php_igbinary.h -> IGBINARY_FORMAT_VERSION = 0x00000002
				 * - igbinary.c -> igbinary_serialize_header()
				 * - igbinary.c -> enum igbinary_type {...}
				 */
				if (self::$igbinary && ord($value[3]) === 2 && ord($value[4]) >= 0 && ord($value[4]) <= 37) {
					$x = igbinary_unserialize($value);
				} elseif (ord($value[3]) === 0) {
					$x = unserialize(substr($value, 3));
				} else {
					$x = FALSE;
				}
				
				if ($x !== FALSE) {
					return $x;
				}
			}
		}
		
		return $value;
	}
}